﻿#include "stdbool.h"
#include "sys_plat.h"
#include "time.h"
#include "ping.h"

static uint16_t ping_checksum16(void *buf, uint16_t len, uint32_t pre_sum, bool complement)
{
    uint16_t *curr_buf = (uint16_t *)buf;
    uint32_t check_sum = pre_sum;

    while (len > 1)
    {
        check_sum += *curr_buf++;
        len -= 2;
    }

    if (len > 0)
    {
        check_sum += *(uint8_t *)curr_buf;
    }

    uint16_t high;
    while ((high = check_sum >> 16) != 0)
    {
        check_sum = high + (check_sum & 0xffff);
    }

    return complement ? (uint16_t)~check_sum : (uint16_t)check_sum;
}

/**
 * @brief 通过协议栈接口向外发送ping命令
 * @param ping 用于存放ping命令相关的数据
 * @param dest 目的ip地址
 * @param count ping命令发送字节数
 * @param interval 间隔interval后发送下一个ping命令，单位ms
 */
void ping_run(ping_t *ping, const char *dest, int count, int size, int interval)
{
    uint16_t start_id = PING_DEFAULT_ID;

    // // todo 先直接调用系统的socket相关接口
    // WSADATA wsdata;
    // WSAStartup(MAKEWORD(2, 2), &wsdata);

    int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    struct timeval time;
    time.tv_sec = 3;
    time.tv_usec = 0;
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char *)&time, sizeof(time));
    if (s < 0)
    {
        printf("open socket faield!\n");
        return;
    }

    struct sockaddr_in addr;
    plat_memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(dest);
    addr.sin_port = 0;

    int fill_size = size > PING_BUFFER_SIZE ? PING_BUFFER_SIZE : size;
    for (int i = 0; i < fill_size; ++i)
    {
        ping->req.buf[i] = i + 100;
    }

    int total_size = sizeof(ping_icmpv4_hdr_t) + fill_size;
    for (int i = 0, seq = 0; i < count; ++i, ++seq)
    {
        ping->req.echo_hdr.type = 8;
        ping->req.echo_hdr.code = 0;
        ping->req.echo_hdr.checksum = 0;
        ping->req.echo_hdr.id = start_id;
        ping->req.echo_hdr.seq = seq;
        *(clock_t *)ping->req.buf = clock();
        ping->req.echo_hdr.checksum = ping_checksum16(&ping->req, total_size, 0, true);

        // 发送数据
        ssize_t size = sendto(s, (const char *)&ping->req, total_size, 0, (const struct sockaddr *)&addr, sizeof(addr));
        if (size < 0)
        {
            printf("send message failed!\n");
            break;
        }

        memset(&ping->reply, 0, sizeof(ping->reply));

        struct sockaddr_in from_addr;
        memset(&from_addr, 0, sizeof(struct sockaddr_in));
        int from_addr_size = sizeof(from_addr);
        do
        {
            size = recvfrom(s, (char *)&ping->reply, sizeof(ping->reply), 0, (struct sockaddr *)&from_addr, &from_addr_size);
            if (size < 0)
            {
                break;
            }
            if (ping->req.echo_hdr.id == ping->reply.echo_hdr.id && ping->req.echo_hdr.seq == ping->reply.echo_hdr.seq)
            {
                ;
                break;
            }
        } while (1);

        int recv_size = size - sizeof(ping_icmpv4_hdr_t) - sizeof(ping_ipv4_hdr_t);
        if (memcmp(ping->req.buf, ping->reply.buf, recv_size) != 0)
        {
            printf("Request timed out.\n");
            continue;
        }

        ping_ipv4_hdr_t *ipv4_hdr = &ping->reply.iphdr;
        int send_size = fill_size;
        if (recv_size == send_size)
        {
            printf("Reply from %s: bytes=%d", inet_ntoa(from_addr.sin_addr), send_size);
        }
        else
        {
            printf("Reply from %s: bytes=%d(send bytes:%d)", inet_ntoa(from_addr.sin_addr), recv_size, send_size);
        }

        clock_t time = *(clock_t *)ping->req.buf;
        int diff_ms = (clock() - time) / (CLOCKS_PER_SEC / 1000);
        // int diff_ms = 0;
        if (diff_ms < 1)
        {
            printf(" times<1ms TTL=%d\n", ipv4_hdr->ttl);
        }
        else
        {
            printf(" times=%dms TTL=%d\n", diff_ms, ipv4_hdr->ttl);
        }

        sys_sleep(interval);
    }

    close(s);
}